package org.example.uitls;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import org.example.hutool.ReUtilController;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Java SE 正则表达式 API Pattern 与 Matcher
 * 1、推荐使用 Hutool 的 Validator {@link org.example.hutool.other.ValidatorTest}、ReUtil {@link ReUtilController}
 *
 * @author wangMaoXiong
 * @version 1.0
 * @date 2023/1/11 17:03
 */
@SuppressWarnings("All")
public class PatternTest {

    private static final Logger log = LoggerFactory.getLogger(PatternTest.class);

    /**
     * 什么是单词边界？
     * 单词边界是指一个单词的开头或结尾，或者一个单词和一个非单词字符之间的位置。单词字符通常指字母、数字和下划线，非单词字符则是指除此之外的所有字符。
     * <p>
     * “\b” 可以用于正则表达式中，以表示一个单词的边界。例如，如果要匹配"cat"这个单词，而不是"catch"或"concatenate"中的"cat"，可以使用正则表达式"\bcat\b"。
     * 这样，只有当"cat"前后都是非单词字符或者字符串的开头或结尾时，才会匹配成功。
     * 需要注意的是，“\b"不匹配任何实际的字符，只匹配一个位置，因此在匹配时并不会包括 “\b” 所在的位置。
     */
    @Test
    public void bFindTest1() {
        String text = "update emp t set update_user_code='01',is_deleted=1 where t.is_deleted=2 for update";
        // 查询指定单词
        Pattern pattern1 = Pattern.compile("\\bupdate\\b");
        Matcher matcher1 = pattern1.matcher(text);
        while (matcher1.find()) {
            // pattern1=update
            // pattern1=update
            System.out.println("pattern1=" + matcher1.group());
        }
        // false
        System.out.println(Pattern.compile("\\buser\\b").matcher(text).find());

        Matcher matcher2 = Pattern.compile("\\bis_deleted\\b").matcher(text);
        while (matcher2.find()) {
            // is_deleted
            // is_deleted
            System.out.println(matcher2.group());
        }
        System.out.println("---------------------2-------------");

        String text2 = "leve == @com.example.MyEnum@FIRST";
        String text3 = "leve == liShiMin@com.example.MyEnum@FIRST";
        // false
        System.out.println(Pattern.compile("\\b@com.example.MyEnum").matcher(text2).find());

        Matcher matcher3 = Pattern.compile("\\b@com.example.MyEnum").matcher(text3);
        // true，@com.example.MyEnum
        System.out.println(matcher3.find() + "，" + matcher3.group());

        Matcher matcher4 = Pattern.compile("\\bcom.example.MyEnum").matcher(text2);
        // true，com.example.MyEnum
        System.out.println(matcher4.find() + "，" + matcher4.group());

        Matcher matcher5 = Pattern.compile("@\\bcom.example.MyEnum").matcher(text2);
        // true，@com.example.MyEnum
        System.out.println(matcher5.find() + "，" + matcher5.group());
    }

    @Test
    public void bFindTest2() {
        String text = "来源：央视网 | 2023 年 11 月 28 日 12:19:53。依法查处“ MarryUI_ 相亲交友”等违法违规 App156 款";
        // 匹配所有单词字符内容(\w 与"[A-Za-z0-9_]"等效)
        Pattern pattern1 = Pattern.compile("\\b\\w+\\b");
        Matcher matcher1 = pattern1.matcher(text);
        while (matcher1.find()) {
            // 2023
            // 11
            // 28
            // 12
            // 19
            // 53
            // MarryUI_
            // App156
            System.out.println(matcher1.group());
        }
        System.out.println("--------2-----------");

        String text2 = "来源：央视网|2023年11月28日12:19:53。依法查处“MarryUI_相亲交友”等违法违规App156款";

        // 匹配单词开头
        Pattern pattern2 = Pattern.compile("\\b\\w+");
        Matcher matcher2 = pattern2.matcher(text2);
        while (matcher2.find()) {
            // 2023
            // 19
            // 53
            // MarryUI_
            System.out.println(matcher2.group());
        }
        System.out.println("--------3-----------");

        // 匹配单词结尾
        Pattern pattern3 = Pattern.compile("\\w+\\b");
        Matcher matcher3 = pattern3.matcher(text2);
        while (matcher3.find()) {
            // 12
            // 19
            // 53
            System.out.println(matcher3.group());
        }
        System.out.println("--------4-----------");

        // 匹配单词
        Pattern pattern4 = Pattern.compile("\\w+\\b");
        Matcher matcher4 = pattern4.matcher(text2);
        while (matcher4.find()) {
            // 12
            // 19
            // 53
            System.out.println(matcher4.group());
        }
    }

    @Test
    public void bFindTest3() {
        String text = "来源：央视网 | 2023 年 11 月 28 日 12:19:53。依法查处“ MarryUI_ 相亲交友”等违法违规 App156 款";

        // \B与\b相反，匹配非单词边界(\w 与"[A-Za-z0-9_]"等效)
        Pattern pattern1 = Pattern.compile("\\B\\w+\\B");
        Matcher matcher1 = pattern1.matcher(text);
        while (matcher1.find()) {
            // 02
            // arryUI
            // pp15
            System.out.println(matcher1.group());
        }
        System.out.println("--------2-----------");

        String text2 = "来源：央视网|2023年11月28日12:19:53。依法查处“MarryUI_相亲交友”等违法违规App156款";

        // \B与\b相反，匹配非单词边界
        Pattern pattern2 = Pattern.compile("\\B\\w+");
        Matcher matcher2 = pattern2.matcher(text2);
        while (matcher2.find()) {
            // 023
            // 11
            // 28
            // 12
            // 9
            // 3
            // arryUI_
            // App156
            System.out.println(matcher2.group());
        }
        System.out.println("--------3-----------");

        String text3 = "来源：央视网|2023年11月28日12:19:53。依法查处“MarryUI_相亲交友”等违法违规App156款";
        Pattern pattern3 = Pattern.compile("\\w+\\B");
        Matcher matcher3 = pattern3.matcher(text3);
        while (matcher3.find()) {
            // 2023
            // 11
            // 28
            // 1
            // 1
            // 5
            // MarryUI_
            // App156
            System.out.println(matcher3.group());
        }
    }


    /**
     * static Pattern compile(String regex)	将给定的正则表达式编译为模式
     * Matcher matcher(CharSequence input)	创建一个匹配器，匹配给定的输入与此模式是否匹配。
     */
    @Test
    public void matcherTest1() {
        // [0-9]*：匹配零个或多个数字
        Pattern pattern0_9 = Pattern.compile("[0-9]*");
        Matcher matcher1 = pattern0_9.matcher("314");
        Matcher matcher2 = pattern0_9.matcher("3.14");
        // boolean matches() 	尝试将整个区域与模式匹配。即必须全部匹配才返回 true.
        // true
        System.out.println(matcher1.matches());
        // false
        System.out.println(matcher2.matches());
    }

    /**
     * static boolean matches(String regex, CharSequence input)
     * 编译给定的正则表达式，并尝试匹配给定的输入是否全部匹配。
     */
    @Test
    public void matchesTest1() {
        // "\\w+"：匹配大小写字母、数字、下划线，"\w" 表示单词字符，即 大小写字母、数字、下划线。
        // matches 方法将整个整个字符串与模式进行匹配，匹配成功返回 true，否则 false
        // 第一个反斜杆是转义字符
        // true
        System.out.println(Pattern.matches("\\w+", "Wmx_328"));
        // false
        System.out.println(Pattern.matches("\\w+", "Wmx3.28"));
    }

    /**
     * String[] split(CharSequence input)
     * 将给定的输入序列用正则模式进行匹配分割，未匹配上时，返回整个目标字符串
     * String[] split(CharSequence input, int limit)
     * 限制匹配次数为 limit -1 次，负数和0表示不做限制.
     */
    @Test
    public void splitTest1() {
        // 匹配下划线
        Pattern pattern_ = Pattern.compile("_");
        String input = "89U_78P_G67-8P_长城";
        // 使用正则模式对目标字符串进行分割，limit=0 等同于 split(input)
        // limit=2实质是分割一次，匹配一次后不再进行匹配分割
        String[] split1 = pattern_.split(input);
        String[] split2 = pattern_.split(input, 0);
        String[] split3 = pattern_.split(input, 2);
        // [89U, 78P, G67-8P, 长城]
        System.out.println(Arrays.asList(split1));
        // [89U, 78P, G67-8P, 长城]
        System.out.println(Arrays.asList(split2));
        // [89U, 78P_G67-8P_长城]
        System.out.println(Arrays.asList(split3));
    }

    /**
     * boolean matches() 	尝试将整个区域与模式匹配，即必须全部匹配才返回 true.
     * boolean lookingAt() 	尝试将从区域开头开始的输入序列与该模式匹配，即从头开始匹配，只要头部一部分匹配上就行，不需要整个匹配.
     * * 注意此方法执行之后，匹配器位置已经发生移动。
     * boolean find() 	尝试查找与该模式匹配的输入序列的下一个子序列。即只要其中一部分能匹配上，就返回 true。
     * * 注意此方法执行之后，匹配器位置已经发生移动。
     * boolean find(int start）	重置此匹配器，然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
     * * 注意此方法执行之后，匹配器位置已经发生移动。
     */
    @Test
    public void lookingAtTest1() {
        // 匹配小写字母一个或者多个
        Pattern pattern_a_z = Pattern.compile("[a-z]+");
        // Matcher matcher(CharSequence input)	创建一个匹配器，匹配给定的输入与此模式是否匹配.
        Matcher matcher1 = pattern_a_z.matcher("<span>123</span>");
        Matcher matcher2 = pattern_a_z.matcher("span123");
        Matcher matcher3 = pattern_a_z.matcher("span");
        // true,false,false
        System.out.println(matcher1.find() + "," + matcher1.matches() + "," + matcher1.lookingAt());
        // true,false,true
        System.out.println(matcher2.find() + "," + matcher2.matches() + "," + matcher2.lookingAt());
        // true,true,true
        System.out.println(matcher3.find() + "," + matcher3.matches() + "," + matcher3.lookingAt());
    }

    /**
     * boolean find() 	尝试查找与该模式匹配的输入序列的下一个子序列。即只要其中一部分能匹配上，就返回 true。
     * * 注意此方法执行之后，匹配器位置已经发生移动。
     * boolean find(int start）	重置此匹配器，然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
     * * 注意此方法执行之后，匹配器位置已经发生移动。
     * int start() 	返回上一个匹配的起始索引
     * int start(int group)	返回在上一个匹配操作期间，由给定组所捕获的子序列的初始索引
     * int end()	返回最后一个字符匹配后的偏移量。
     * int end(int group)	返回在上一个匹配操作期间，由给定组所捕获子序列的最后字符之后的偏移量。
     */
    @Test
    public void findGroupTest1() {
        Pattern pattern_a_z = Pattern.compile("[a-z]+");
        Matcher matcher = pattern_a_z.matcher("2020 i love you! 0329");
        while (matcher.find()) {
            // start=5,end=6
            // 	i
            // start=7,end=11
            // 	love
            // start=12,end=15
            // 	you
            System.out.println("start=" + matcher.start() + ",end=" + matcher.end());
            System.out.println("\t" + matcher.group(0));
        }
    }

    /**
     * 从原内容中获取第一个数字内容
     */
    @Test
    public void findGroupTest2() {
        Pattern pattern_a_z = Pattern.compile("\\d+");
        Matcher matcher = pattern_a_z.matcher("中国人 2020 i love you! 0329");
        if (matcher.find()) {
            // start=4,end=8
            // 	2020
            System.out.println("start=" + matcher.start() + ",end=" + matcher.end());
            System.out.println("\t" + matcher.group(0));
        }
    }

    /**
     * Matcher appendReplacement(StringBuffer sb, String replacement)	实现非终端添加和替换步骤。
     * StringBuffer appendTail(StringBuffer sb)	实现终端添加和替换步骤。
     * String replaceAll(String replacement)	替换匹配到的输入字符串中的每个子序列。
     * String replaceFirst(String replacement)	 替换匹配到的输入字符串中的第一个子序列。
     * static String quoteReplacement(String s)	返回指定字符串的字面替换字符串。这个方法返回一个字符串，就像传递给Matcher类的appendReplacement 方法一个字面字符串一样工作。
     */
    @Test
    public void replaceAllTest1() {
        // [] 表示字符集，匹配其中包含的任一字符
        Pattern pattern_code = Pattern.compile("[<>\\w'/=:]");
        String input = "<span style='color:red'>万里长城</span>";
        Matcher matcher = pattern_code.matcher(input);

        boolean find = matcher.find();
        // 替换匹配到的所有子序列为空，输出：find = true, 万里长城
        String replaceAll = matcher.replaceAll("");
        // find = true, 万里长城
        System.out.println("find = " + find + "," + replaceAll);

        // 重置匹配器，重新开始匹配
        matcher.reset();

        // 注意：虽然 matchers 返回 false，但是对于匹配上的目标照样能进行替换
        // 输出：matches = false, 万里长城
        boolean matches = matcher.matches();
        String replaceAll2 = matcher.replaceAll("");
        // matches = false, 万里长城
        System.out.println("matches = " + matches + "," + replaceAll2);

        // replaceFirst：只替换匹配的第一个，输出：span style='color:red'>万里长城</span>
        System.out.println(matcher.replaceFirst(""));
    }

    /**
     * 分组取值
     */
    @Test
    public void matcheGroup1() {
        String text = " delete  from emp t ";
        text = text.trim().toLowerCase();
        Pattern pattern1 = Pattern.compile("(delete)|(^delete)\\s+[\\s\\S]*|[\\s\\S]*\\s+(delete$)");
        Matcher matcher = pattern1.matcher(text);
        if (matcher.matches()) {
            // group(0)=delete  from emp t
            // 1=null
            // 2=delete
            // 3=null
            System.out.println("group(0)=" + matcher.group(0));
            for (int i = 1; i <= matcher.groupCount(); i++) {
                System.out.println((i) + "=" + matcher.group(i));
            }
        }
    }

    /**
     * 提取文本中的时间
     *
     * @param input ；含有日期的文本，如 网易 2020-03-29 10:45:00 来源: 央视新闻客户端
     * @return ：返回提取好的时间，如 20200329104500
     */
    public String parserDate(String input) {
        String result = null;

        // Pattern 类是线程安全的
        // pattern1：提取 yyyy-MM-dd HH:mm:ss
        // pattern2：提取 yyyy-MM-dd HH:mm
        // pattern3：提取 yyyy-MM-dd

        Pattern pattern1 = Pattern.compile("[\\s\\W]*(\\d{4})[-年_\\s](\\d{1,2})[-月_\\s](\\d{1,2})[\\s\\W]*(\\d{1,2})[:：时](\\d{1,2})[:：分](\\d{1,2})[\\s\\W]*");
        Pattern pattern2 = Pattern.compile("[\\s\\W]*(\\d{4})[-年_\\s](\\d{1,2})[-月_\\s](\\d{1,2})[\\s\\W]*(\\d{1,2})[:：时](\\d{1,2})[\\s\\W]*");
        Pattern pattern3 = Pattern.compile("[\\s\\W]*(\\d{4})[-年_\\s](\\d{1,2})[-月_\\s](\\d{1,2})[\\s\\W]*");

        // 无论哪个规则匹配上，都赋值给公共的 matcher，用于后续统一处理
        Matcher matcher1 = pattern1.matcher(input);
        Matcher matcher2 = pattern2.matcher(input);
        Matcher matcher3 = pattern3.matcher(input);
        Matcher matcher = null;
        //注意只有先匹配上正则表达式，才能用 matcher.group 取值.
        if (matcher1.find()) {
            matcher = matcher1;
        } else if (matcher2.find()) {
            matcher = matcher2;
        } else if (matcher3.find()) {
            matcher = matcher3;
        }
        if (matcher != null) {
            //待取值的分组个数，值先用 list 存储
            int groupCount = matcher.groupCount();
            List<String> dataList = new ArrayList<>(6);
            //注意 group 索引从1开始，按顺序匹配正则中的 ()
            for (int i = 0; i < groupCount; i++) {
                String group = matcher.group(i + 1);
                //对于 月、天、时、分、秒 很可能出现单位数，如 2018-8-8 3:2:6，所以需要补齐
                group = group.length() == 1 ? "0" + group : group;
                dataList.add(group);
            }
            //最后对于不满足 yyyyMMddHHmmss 的，比如 2018-8-8，后续统一用 00 代替
            for (int j = dataList.size(); j < 6; j++) {
                dataList.add("00");
            }
            //直接将 list 转字符串 [2020, 03, 29, 10, 45, 00]，然后去掉其中不需要的字符 [、]、, 以及空格
            result = dataList.toString().replaceAll("[\\[\\],\\s]", "");
        }
        return result;
    }

    @Test
    public void parserDateTest() {
        // 20200329104500
        System.out.println(parserDate("网易 2020-03-29 10:45:00 来源: 央视新闻客户端"));
        // 20200309034000
        System.out.println(parserDate("搜狐 2020-03-9 3:40 来源:环球网"));
        // 20200328200823
        System.out.println(parserDate("凤凰 2020年3月28日 20:8:23 "));
        // 20200108000000
        System.out.println(parserDate("腾讯 2020年1月8日  今天天气晴."));
        // null
        System.out.println(parserDate("腾讯 2020年的1月8日号  今天天气晴."));
    }

    @Test
    public void patternTest() {

        /**只能输入数字："^[0-9]*$" */
        String patternStr1 = "^\\d+$";
        Pattern pattern1 = Pattern.compile(patternStr1);
        String testStr1 = "89。6";
        Matcher matcher1 = pattern1.matcher(testStr1);
        //false
        System.out.println("matcher1=" + matcher1.find());

        /**只能输入n位的数字："^\d{n}$" */
        String patternStr2 = "^\\d{3}$";
        Pattern pattern2 = Pattern.compile(patternStr2);
        String testStr2 = "896";
        Matcher matcher2 = pattern2.matcher(testStr2);
        //true
        System.out.println("matcher2=" + matcher2.find());

        /**只能输入至少n位的数字："^\d{n,}$" */
        String patternStr3 = "^\\d{3,}$";
        Pattern pattern3 = Pattern.compile(patternStr3);
        String testStr3 = "89";
        Matcher matcher3 = pattern3.matcher(testStr3);
        //false
        System.out.println("matcher3=" + matcher3.find());


        /**只能输入[m,n]位的数字：。"^\d{m,n}$" */
        String patternStr4 = "^\\d{3,5}$";
        Pattern pattern4 = Pattern.compile(patternStr4);
        String testStr4 = "895556";
        Matcher matcher4 = pattern4.matcher(testStr4);
        //false
        System.out.println("matcher4=" + matcher4.find());

        /**只能输入整数或者有两位小数的正实数："^[0-9]+(\.[0-9]{2})?$" */
        String patternStr5 = "^\\d+(\\.\\d{2})?$";
        Pattern pattern5 = Pattern.compile(patternStr5);
        String testStr5 = "89556";
        Matcher matcher5 = pattern5.matcher(testStr5);
        //true
        System.out.println("matcher5=" + matcher5.find());

        /**只能输入整数或者有1~3位小数的正实数:"^[0-9]+(\.[0-9]{1,3})?$" */
        String patternStr6 = "^\\d+(\\.\\d{1,3})?$";
        Pattern pattern6 = Pattern.compile(patternStr6);
        String testStr6 = "8.9556";
        Matcher matcher6 = pattern6.matcher(testStr6);
        //false
        System.out.println("matcher6=" + matcher6.find());

        /**只能输入非零的正整数："^\+?[1-9][0-9]*$" */
        String patternStr7 = "^\\+?[1-9]\\d*$";
        Pattern pattern7 = Pattern.compile(patternStr7);
        String testStr7 = "+89556";
        Matcher matcher7 = pattern7.matcher(testStr7);
        //true
        System.out.println("matcher7=" + matcher7.find());

        /**只能输入非零的负整数："^\-[1-9][0-9]*$" */
        String patternStr8 = "^\\-[1-9]\\d*$";
        Pattern pattern8 = Pattern.compile(patternStr8);
        String testStr8 = "89556";
        Matcher matcher8 = pattern8.matcher(testStr8);
        //false
        System.out.println("matcher8=" + matcher8.find());

        /**只能输入长度为3的字符："^.{3}$"，"."表示 任何字符，不需要转义*/
        String patternStr9 = "^.{3}$";
        Pattern pattern9 = Pattern.compile(patternStr9);
        String testStr9 = "8a9";
        Matcher matcher9 = pattern9.matcher(testStr9);
        //true
        System.out.println("matcher9=" + matcher9.find());

        /** 只能输入由26个英文字母组成的字符串:"^[A-Za-z]+$"*/
        String patternStr10 = "^[a-z|A-Z]+$";
        Pattern pattern10 = Pattern.compile(patternStr10);
        String testStr10 = "uiA.";
        Matcher matcher10 = pattern10.matcher(testStr10);
        //false
        System.out.println("matcher10=" + matcher10.find());

        /**只能输入由数字和26个英文字母组成的字符串:"^[A-Za-z0-9]+$"*/
        String patternStr11 = "^[A-Za-z0-9]+$";
        Pattern pattern11 = Pattern.compile(patternStr11);
        String testStr11 = "uiA23jk";
        Matcher matcher11 = pattern11.matcher(testStr11);
        System.out.println("//true=" + matcher11.find());

        /**只能输入由数字、26个英文字母或者下划线组成的字符串："^\w+$" */
        String patternStr12 = "^\\w+$";
        Pattern pattern12 = Pattern.compile(patternStr12);
        String testStr12 = "uiA23jk_io";
        Matcher matcher12 = pattern12.matcher(testStr12);
        //true
        System.out.println("matcher12=" + matcher12.find());

        /**验证用户密码：以字母或下划线开头，只能包含字符、数字和下划线,长度在6~18之间："^[a-zA-Z_]\w{5,17}$" */
        String patternStr13 = "^[A-Za-z_]\\w{5,17}$";
        Pattern pattern13 = Pattern.compile(patternStr13);
        String testStr13 = "_90uiA23jk_io";
        Matcher matcher13 = pattern13.matcher(testStr13);
        //true
        System.out.println("matcher13=" + matcher13.find());

        /**只能输入汉字："^[\u4e00-\u9fa5]{0,}$" */
        String patternStr14 = "^[\\u4e00-\\u9fa5]+$";
        Pattern pattern14 = Pattern.compile(patternStr14);
        String testStr14 = "武即可汉";
        Matcher matcher14 = pattern14.matcher(testStr14);
        //true
        System.out.println("matcher14=" + matcher14.find());

        /**验证Email地址："^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$" */
        String patternStr15 = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
        Pattern pattern15 = Pattern.compile(patternStr15);
        String testStr15 = "226461750@163.com";
        Matcher matcher15 = pattern15.matcher(testStr15);
        //true
        System.out.println("matcher15=" + matcher15.find());

        /**验证InternetURL："^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$" */
        String patternStr16 = "^http://([\\w-]+\\.)+[\\w-]+(/[\\w\\-\\./?%&=]*)?$";
        Pattern pattern16 = Pattern.compile(patternStr16);
        String testStr16 = "http://www.baidu.com";
        Matcher matcher16 = pattern16.matcher(testStr16);
        //true
        System.out.println("matcher16=" + matcher16.find());
    }

    /**
     * SQL注入检测——原理分析
     * 1、假设需要防止前端传入的内容中不能有"delete"操作关键字(不区分大小写，其他关键字也是同理)。
     * 2、简单使用字符串包含是不行的，比如：
     * * contains("delete") -> 会误判 delete_user_name
     * * contains("delete ") -> 会误判 is_delete
     * * contains(" delete ") -> 又会把真正需要拦截的"delete from"给放走。
     * 3、在正则匹配之前，去掉原内容前后的空格后，然后统一转成小写进行匹配，一共有下面四种情况含有需要拦截的目标关键字。
     * <pre>
     *      "delete"
     *      "delete xxx"
     *      "xxx delete xxx"
     *      "xxx delete"
     * </pre>
     */
    @Test
    public void sqlInjectTest1() {
        String text = "sds   from emp t DELETE ";
        text = text.trim().toLowerCase();
        Pattern pattern = Pattern.compile("(delete)|(^delete\\s+[\\s\\S]*)|([\\s\\S]*\\s+delete$)|([\\s\\S]*\\s+delete\\s+[\\s\\S]*)");
        Matcher matcher = pattern.matcher(text);
        if (matcher.matches()) {
            System.out.println(matcher.groupCount());
            System.out.println("group(0)=" + matcher.group(0));
            for (int i = 1; i <= matcher.groupCount(); i++) {
                System.out.println((i) + "=" + matcher.group(i));
            }
        }
    }

    /**
     * SQL注入拦截检测工具方法——使用matches匹配
     * 1、在正则匹配之前，去掉原内容前后的空格后，然后统一转成小写进行匹配，一共有下面四种情况含有需要拦截的目标关键字。
     * <pre>
     *      "delete"
     *      "delete xxx"
     *      "xxx delete xxx"
     *      "xxx delete"
     * </pre>
     *
     * @param text ：需要被检测的内容
     * @return
     */
    public static final boolean sqlInjectMatches(String text) {
        if (StrUtil.isBlank(text)) {
            return false;
        }
        text = text.trim().toLowerCase();
        List<String> keywords = new ArrayList<>(64);
        keywords.addAll(Lists.newArrayList("alter", "ascii", "call", "create", "declare", "delete", "delimiter", "drop", "exec", "execute"));
        keywords.addAll(Lists.newArrayList("exp", "expdp", "flashback ", "grant", "insert", "imp", "into", "impdp", "information_schema.columns"));
        keywords.addAll(Lists.newArrayList("merge", "set", "select", "source", "truncate", "table_schema", "update", "use", "xp_cmdshell"));
        Set<String> failKeys = new TreeSet<>();
        for (String keyword : keywords) {
            keyword = keyword.trim().toLowerCase();
            String patt = "(" + keyword + ")|(^" + keyword + ")\\s+[\\s\\S]*|[\\s\\S]*\\s+(" + keyword + "$)|[\\s\\S]*\\s+(" + keyword + ")\\s+[\\s\\S]*";
            Pattern pattern = Pattern.compile(patt);
            Matcher matcher = pattern.matcher(text);
            if (matcher.matches()) {
                failKeys.add(keyword);
            }
        }
        if (failKeys.size() > 0) {
            log.error("SQL注入风险提示：{}，请检查以下内容：{}", failKeys, text);
            return true;
        }
        return false;
    }

    /**
     * SQL注入拦截检测工具方法——使用find匹配
     * 1、在正则匹配之前，去掉原内容前后的空格后，然后统一转成小写进行匹配，一共有下面四种情况含有需要拦截的目标关键字。
     * <pre>
     *      "delete"
     *      "delete xxx"
     *      "xxx delete xxx"
     *      "xxx delete"
     * </pre>
     *
     * @param text ：需要被检测的内容
     * @return
     */
    public static final boolean sqlInjectFind(String text) {
        if (StrUtil.isBlank(text)) {
            return false;
        }
        text = text.trim().toLowerCase();
        // 需要拦截的关键字
        List<String> keywords = new ArrayList<>(64);
        keywords.addAll(Lists.newArrayList("alter", "ascii", "call", "create", "declare", "delete", "delimiter", "drop", "exec", "execute"));
        keywords.addAll(Lists.newArrayList("exp", "expdp", "flashback ", "grant", "insert", "imp", "into", "impdp", "information_schema.columns"));
        keywords.addAll(Lists.newArrayList("merge", "set", "select", "source", "truncate", "table_schema", "update", "use", "xp_cmdshell"));
        Set<String> failKeys = new TreeSet<>();
        for (String keyword : keywords) {
            keyword = keyword.trim().toLowerCase();
            String patt = "^" + keyword + "$|^" + keyword + "\\s+|\\s+" + keyword + "$|\\s+" + keyword + "\\s+";
            Pattern pattern = Pattern.compile(patt);
            Matcher matcher = pattern.matcher(text);
            if (matcher.find()) {
                failKeys.add(keyword);
            }
        }
        if (failKeys.size() > 0) {
            log.error("SQL注入风险提示：{}，请检查以下内容：{}", failKeys, text);
            return true;
        }
        return false;
    }

    /**
     * SQL 注入检测 ————推荐方式
     *
     * @param text ：被检测的内容，比如 update emp t set update_user_code='01',is_deleted=1 where t.is_deleted=2 for update
     * @return ：返回校验未通过的关键字，比如 [set, update]
     */
    public static final List<String> sqlInjectFindV2(String text) {
        Set<String> failKeys = new TreeSet<>();
        if (StrUtil.isBlank(text)) {
            return ListUtil.toList(failKeys);
        }
        text = text.trim().toLowerCase();
        // 检测目标内容是否含有这些关键字
        String keywords = "\\b(alter|ascii|call|create|declare|delete|delimiter|drop|exec|execute|" +
                "exp|expdp|flashback |grant|insert|imp|into|impdp|information_schema.columns|" +
                "merge|set|select|source|truncate|table_schema|update|use|xp_cmdshell)\\b";
        Pattern pattern = Pattern.compile(keywords);
        Matcher matcher = pattern.matcher(text);
        while (matcher.find()) {
            // 收集匹配到的关键字
            failKeys.add(matcher.group());
        }
        return ListUtil.toList(failKeys);
    }

    /**
     * SQL 注入检测
     *
     * @param text ：被检测的内容，比如 update emp t set update_user_code='01',is_deleted=1 where t.is_deleted=2 for update
     * @return ：返回校验是否通过的结果
     */
    public static final boolean sqlInjectMatchesV2(String text) {
        Set<String> failKeys = new TreeSet<>();
        if (StrUtil.isBlank(text)) {
            return false;
        }
        text = text.trim().toLowerCase();
        // 检测目标内容是否含有这些关键字，
        String keywords = "[\\s\\S]*\\b(alter|ascii|call|create|declare|delete|delimiter|drop|exec|execute|" +
                "exp|expdp|flashback |grant|insert|imp|into|impdp|information_schema.columns|" +
                "merge|set|select|source|truncate|table_schema|update|use|xp_cmdshell)\\b[\\s\\S]*";
        Pattern pattern = Pattern.compile(keywords);
        Matcher matcher = pattern.matcher(text);
        return matcher.matches();
    }

    @Test
    public void sqlInjectMatchesV2Test2() {
        String text = "update emp t set update_user_code='01',is_deleted=1 where t.is_deleted=2 for update";
        // true
        System.out.println(sqlInjectMatchesV2(text));

        // true
        System.out.println(sqlInjectMatchesV2("delete from emp where update_user_code='01'"));

        // true
        System.out.println(sqlInjectMatchesV2("select\n" +
                " from emp where update_user_code='01' for\n " +
                "update"));

        // true
        System.out.println(sqlInjectMatchesV2("SELECT count(1)\n" +
                "  FROM BAS_MOF_DEP\n" +
                " WHERE IS_ENABLED = '1'\n" +
                "   AND MOF_DIV_CODE IS NOT NULL\n" +
                "   AND IS_DELETED = '2'\n" +
                "   AND (NOT REGEXP_LIKE(MOF_DIV_CODE, '^[0-9]{9}$') OR\n" +
                "       SUBSTR(MOF_DIV_CODE, 3, 2) IN ('98', '99') OR\n" +
                "       SUBSTR(MOF_DIV_CODE, 5, 2) IN ('98', '99') OR\n" +
                "       SUBSTR(MOF_DIV_CODE, 7, 3) IN ('998', '999'))\n" +
                "   AND ROWNUM < 6"));

        // false
        System.out.println(sqlInjectMatchesV2("select_ * from emp forupdate"));
    }

    @Test
    public void sqlInjectFindTest2() {
        String text = "update emp t set update_user_code='01',is_deleted=1 where t.is_deleted=2 for update";
        // [set, update]
        System.out.println(sqlInjectFindV2(text));

        // [delete]
        System.out.println(sqlInjectFindV2("delete from emp where update_user_code='01'"));

        // [select, update]
        System.out.println(sqlInjectFindV2("select\n" +
                " from emp where update_user_code='01' for\n " +
                "update"));

        // [select]
        System.out.println(sqlInjectFindV2("SELECT count(1)\n" +
                "  FROM BAS_MOF_DEP\n" +
                " WHERE IS_ENABLED = '1'\n" +
                "   AND MOF_DIV_CODE IS NOT NULL\n" +
                "   AND IS_DELETED = '2'\n" +
                "   AND (NOT REGEXP_LIKE(MOF_DIV_CODE, '^[0-9]{9}$') OR\n" +
                "       SUBSTR(MOF_DIV_CODE, 3, 2) IN ('98', '99') OR\n" +
                "       SUBSTR(MOF_DIV_CODE, 5, 2) IN ('98', '99') OR\n" +
                "       SUBSTR(MOF_DIV_CODE, 7, 3) IN ('998', '999'))\n" +
                "   AND ROWNUM < 6"));

        // []
        System.out.println(sqlInjectFindV2("select_ * from emp forupdate"));
    }

    @Test
    public void sqlInjectMatchesTest() {
        // true
        System.out.println(PatternTest.sqlInjectMatches("update emp t set ename='xxx'"));
        // false
        System.out.println(PatternTest.sqlInjectMatches("update_user_name='张三'"));
        // true
        System.out.println(PatternTest.sqlInjectMatches("SELECT * FROM EMP T WHERE 1=1 FOR UPDATE "));
        // true
        System.out.println(PatternTest.sqlInjectMatches("  DROP table emo;"));
        // false
        System.out.println(PatternTest.sqlInjectMatches("dropped=true"));
        // true
        System.out.println(PatternTest.sqlInjectMatches("delete from emp"));
        // false
        System.out.println(PatternTest.sqlInjectMatches("is_deleted=1"));
        // true
        System.out.println(PatternTest.sqlInjectMatches("USE"));
        // false
        System.out.println(PatternTest.sqlInjectMatches("USER"));
    }

    @Test
    public void sqlInjectFindTest() {
        // true
        System.out.println(PatternTest.sqlInjectFind("UPDATE EMP T SET ENAme='xxx'"));
        // false
        System.out.println(PatternTest.sqlInjectFind("update_user_name='张三'"));
        // true
        System.out.println(PatternTest.sqlInjectFind("SELECT * FROM EMP T WHERE 1=1 FOR UPDATE "));
        // true
        System.out.println(PatternTest.sqlInjectFind("  " +
                "drop" +
                " table emo;"));
        // false
        System.out.println(PatternTest.sqlInjectFind("dropped=true"));
        // true
        System.out.println(PatternTest.sqlInjectFind("DELETE" +
                " from emp"));
        // false
        System.out.println(PatternTest.sqlInjectFind("is_deleted=1"));
        // true
        System.out.println(PatternTest.sqlInjectFind("USE"));
        // false
        System.out.println(PatternTest.sqlInjectFind("USER"));
    }

    /**
     * 查询SQL中的注释信息
     *
     * @param sql
     */
    public static void findSqlComments(String sql) {
        // 默认情况下，.字符不会匹配换行符，但在Pattern.DOTALL模式下，.将会匹配任意字符，包括换行符。
        // 在处理包含换行的文本时，使用Pattern.DOTALL模式可以确保正则表达式能够匹配整个文本，而不仅仅是第一行。
        // 所以单行注释时不能使用DOTALL模式，而多行注释时，必须使用DOTALL模式。
        Pattern LINE_COMMENT_PATTERN = Pattern.compile("--.*");
        Pattern BLOCK_COMMENT_PATTERN = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL);

        Matcher lineCommentMatcher = LINE_COMMENT_PATTERN.matcher(sql);
        while (lineCommentMatcher.find()) {
            System.out.println("单行注释: " + lineCommentMatcher.group());
        }
        Matcher blockCommentMatcher = BLOCK_COMMENT_PATTERN.matcher(sql);
        while (blockCommentMatcher.find()) {
            System.out.println("多行注释: " + blockCommentMatcher.group());
        }
    }

    @Test
    public void testFindSqlComments() {
        String sql = "SELECT * FROM users WHERE id = 1; -- 查询ID为1的用户\n"
                + "/* 查询全部订单 \n" +
                "注意性能，建议分页查询 */\n"
                + "SELECT * FROM orders where 1=?;--args=1\n" +
                "--\n" +
                "/**/";
        // 单行注释: -- 查询ID为1的用户
        // 单行注释: --args=1
        // 单行注释: --
        // 多行注释: /* 查询全部订单
        // 注意性能，建议分页查询 */
        // 多行注释: /**/
        PatternTest.findSqlComments(sql);
    }

}
